#ifndef ARRAY_HPP
#define ARRAY_HPP

#include <std/cstddef>
#include <cpp/new.hpp>
#include <cpp/utility.hpp>
template <typename T, size_t N>
class Array
{
	public:
		typedef T ValueType;                    ///< Contained value type.
		typedef ValueType& Reference;           ///< Reference to value type.
		typedef const ValueType& ConstReference;///< Const reference.
		typedef ValueType* Pointer;             ///< Pointer to value type.
		typedef const ValueType* ConstPointer;  ///< Const pointer.
		typedef size_t SizeType;                ///< The type of the size.
		typedef ptrdiff_t DifferenceType;       ///< The type used for indexing.
		typedef T* Iterator;                    ///< Iterator type.
		typedef const T* ConstIterator;         ///< Const iterator type.
		Array();

		/*Constructs the array by copying \a value to each element in the
		array. We take the copy by value, since we will do N copy in all cases, 
		so another one does not matter, and it makes the interface simpler.
		\param value The value to copy.*/
		Array(T value);

		//Constructs the array by forwarding args to the constructor of each element in the array.
		template <typename... Args>
		Array(Args&&... args);
		//Destructor.
		~Array();
		//Gets an iterator to the first element of the array.
		Iterator begin();
		//Gets an iterator to the past-the-end element of the array.
		Iterator end();
		//Gets a constant iterator to the first element of the array.
		ConstIterator cbegin();
		//Gets a constant iterator to the past-the-end element of the array.
		ConstIterator cend();
		//Accesses the array at position \a pos.
		Reference operator[](SizeType pos);
		//Accesses the array at position \a pos.
		constexpr ConstReference operator[](SizeType pos) const;
		//Accesses the array at position \a pos.
		Reference at(SizeType pos);
		//Accesses the array at position \a pos.
		constexpr ConstReference at(SizeType pos) const;
		//Accesses the first element of the array.
		Reference front();
		//Accesses the first element of the array.
		constexpr ConstReference front() const;
		//Accesses the last element of the array.
		Reference back();
		//Accesses the last element of the array.
		constexpr ConstReference back() const;
		//Checks whether the array is empty, i.e. if its template parameter
		constexpr bool empty() const;
		//Gets the size of the array.
		constexpr SizeType size() const;

	private:
		char _array[N * sizeof(T)];
		T* const _pointer{reinterpret_cast<T*>(&_array)};
};

template <typename T, size_t N>
Array<T, N>::Array():
	Array(T())
{
}

template <typename T, size_t N>
Array<T, N>::Array(T value)
{
	T* pointer{_pointer};
	for(size_t i{0}; i < N; ++i)
		new (static_cast<void*>(pointer++)) T(value);
}

template <typename T, size_t N>
template <typename... Args>
Array<T, N>::Array(Args&&... args):
	Array(T(forward<Args>(args)...))
{
}

template <typename T, size_t N>
Array<T, N>::~Array()
{
	T* pointer{_pointer};
	for(size_t i{0}; i < N; ++i)
		(pointer++)->~T();
}

template <typename T, size_t N>
typename Array<T, N>::Iterator Array<T, N>::begin()
{
	return _pointer;
}

template <typename T, size_t N>
typename Array<T, N>::Iterator Array<T, N>::end()
{
	return _pointer + size();
}

template <typename T, size_t N>
typename Array<T, N>::ConstIterator Array<T, N>::cbegin()
{
	return _pointer;
}

template <typename T, size_t N>
typename Array<T, N>::ConstIterator Array<T, N>::cend()
{
	return _pointer + size();
}

template <typename T, size_t N>
typename Array<T, N>::Reference Array<T, N>::operator[](SizeType pos)
{
	return _pointer[pos];
}

template <typename T, size_t N>
constexpr typename Array<T, N>::ConstReference Array<T, N>::operator[](SizeType pos) const
{
	return _pointer[pos];
}

template <typename T, size_t N>
typename Array<T, N>::Reference Array<T, N>::at(SizeType pos)
{
	return _pointer[pos];
}

template <typename T, size_t N>
constexpr typename Array<T, N>::ConstReference Array<T, N>::at(SizeType pos) const
{
	return _pointer[pos];
}

template <typename T, size_t N>
typename Array<T, N>::Reference Array<T, N>::front()
{
	return _pointer[0];
}

template <typename T, size_t N>
constexpr typename Array<T, N>::ConstReference Array<T, N>::front() const
{
	return _pointer[0];
}

template <typename T, size_t N>
typename Array<T, N>::Reference Array<T, N>::back()
{
	return _pointer[size() - 1];
}

template <typename T, size_t N>
constexpr typename Array<T, N>::ConstReference Array<T, N>::back() const
{
	return _pointer[size() - 1];
}

template <typename T, size_t N>
constexpr bool Array<T, N>::empty() const
{
	return size() == 0;
}

template <typename T, size_t N>
constexpr typename Array<T, N>::SizeType Array<T, N>::size() const
{
	return N;
}

#endif
